package org.hope.ontology.owlApi;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.hope.nlp.SearchTerm;
import org.hope.ontology.OntoTerm;
import org.hope.util.Constants;
import org.hope.util.HopeLogger;
import org.hope.util.ReturnData;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLAnnotationValue;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLIndividual;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLNamedObject;
import org.semanticweb.owlapi.model.OWLObjectHasValue;
import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectPropertyExpression;
import org.semanticweb.owlapi.model.OWLObjectUnionOf;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.reasoner.ConsoleProgressMonitor;
import org.semanticweb.owlapi.reasoner.NodeSet;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerConfiguration;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.semanticweb.owlapi.reasoner.SimpleConfiguration;
import org.semanticweb.owlapi.util.SimpleIRIMapper;

import uk.ac.ebi.ontocat.OntologyService;
import uk.ac.ebi.ontocat.OntologyServiceException;
import uk.ac.ebi.ontocat.OntologyTerm;
import uk.ac.ebi.ontocat.file.FileOntologyService;
import uk.ac.ebi.ontocat.file.ReasonedFileOntologyService;
import uk.ac.ebi.ontocat.virtual.CompositeDecorator;
import uk.ac.manchester.cs.jfact.JFactFactory;

/**
 * @author Thilanka
 */
public class OwlApiOntologyManager implements OntologyManager
{
	/** owlapi ontology manager */
	private OWLOntologyManager owlOntologyManager;

	/** ontology */
	private OWLOntology owlOntology;
	protected OWLDataFactory dataFactory;
	protected OWLReasoner reasoner;

	/** ontocat service */
	private OntologyService ontologyService;

	@Override
	public ReturnData<String> init()
	{
		ReturnData<String> returnData = new ReturnData<String>( Constants.SUCCESS, "" );
		long t = System.currentTimeMillis();
		try
		{
			loadOntologies();
			loadOntocatService();
		}
		catch ( Exception e )
		{
			e.printStackTrace();
			returnData.setNo( Constants.ERROR );
			returnData.setMsg( e.getMessage() );
		}

		HopeLogger.log( "## OWL OntologyManager initialized in " + ( System.currentTimeMillis() - t ) / 1000 + " seconds" );
		return returnData;
	}

	private void loadOntologies() throws OWLOntologyCreationException
	{
		HashMap<String, File> map = new HashMap<String, File>();

		map.put( TRAVEL_IRI, new File( TRAVEL_ONTOLOGY ) );
		map.put( TRAVEL_FACT_IRI, new File( TRAVEL_FACT_ONTOLOGY ) );
		map.put( TRAVEL_ANNOTATION_IRI, new File( TRAVEL_ANNOTATION_ONTOLOGY ) );

		owlOntologyManager = OWLManager.createOWLOntologyManager();

		for ( Entry<String, File> entry : map.entrySet() )
		{
			owlOntologyManager.addIRIMapper( new SimpleIRIMapper( IRI.create( entry.getKey() ), IRI.create( entry.getValue() ) ) );
		}

		IRI defaultIRI = IRI.create( TRAVEL_ANNOTATION_IRI );
		owlOntology = owlOntologyManager.loadOntology( defaultIRI );

		initializeReasoner();
	}

	private void initializeReasoner()
	{
		// initialize other parameters
		dataFactory = owlOntologyManager.getOWLDataFactory();

		// using Fact++ as reasoner
		OWLReasonerFactory reasonerFactory = new JFactFactory();
		ConsoleProgressMonitor progressMonitor = new ConsoleProgressMonitor();
		OWLReasonerConfiguration config = new SimpleConfiguration( progressMonitor );
		reasoner = reasonerFactory.createReasoner( owlOntology, config );
		reasoner.precomputeInferences();

		boolean consistent = reasoner.isConsistent();
		if ( !consistent )
		{
			throw new RuntimeException( "Reasoner error!" );
		}

	}

	public ArrayList<OWLNamedObject> getAnnotationPropertyValueObjects( OWLEntity entity, OWLAnnotationProperty property )
	{
		Set<OWLAnnotation> annots = entity.getAnnotations( owlOntology, property );
		if ( annots.size() > 0 )
		{
			ArrayList<OWLNamedObject> ret = new ArrayList<OWLNamedObject>();
			for ( OWLAnnotation annot : annots )
			{
				OWLAnnotationValue val = annot.getValue();
				if ( val instanceof IRI )
				{
					OWLClass cls = dataFactory.getOWLClass( ( IRI ) val );
					if ( cls.hashCode() != 0 )
					{
						ret.add( cls );
					}
					else
					{
						OWLIndividual ind = dataFactory.getOWLNamedIndividual( ( IRI ) val );
						if ( ind.hashCode() != 0 )
						{
							ret.add( ind.asOWLNamedIndividual() );
						}
					}
				}
			}
			return ret;
		}
		else
		{
			return null;
		}
	}

	public String getAnnotationPropertyValueString( OWLEntity entity, OWLAnnotationProperty property )
	{
		Set<OWLAnnotation> annots = entity.getAnnotations( owlOntology, property );
		return getAnnotationPropertyValueString( annots, property );
	}

	private String getAnnotationPropertyValueString( Set<OWLAnnotation> annots, OWLAnnotationProperty property )
	{
		if ( annots.size() > 0 )
		{
			OWLAnnotation annot = annots.iterator().next();
			return annot.getValue().toString().replaceAll("\"","");
		}
		else
		{
			return null;
		}
	}

	public OWLAnnotationProperty getOWLAnnotationProperty( String iri )
	{
		OWLAnnotationProperty property = dataFactory.getOWLAnnotationProperty( IRI.create( iri ) );
		return property;
	}

	public OWLClass getOWLClass( String iri )
	{
		OWLClass cls = dataFactory.getOWLClass( IRI.create( iri ) );
		return cls;
	}

	public OWLObjectUnionOf getOWLObjectUnionOf( Set<OWLClassExpression> expressions )
	{
		return dataFactory.getOWLObjectUnionOf( expressions );
	}

	public OWLObjectIntersectionOf getOWLObjectIntersectionOf( Set<OWLClassExpression> expressions )
	{
		return dataFactory.getOWLObjectIntersectionOf( expressions );
	}

	public OWLObjectHasValue getOWLObjectHasValue( OWLObjectPropertyExpression property, OWLNamedIndividual value )
	{
		return dataFactory.getOWLObjectHasValue( property, value );
	}

	public Set<OWLNamedIndividual> getMatchingIndividuals( String strType, String strRelation, String strValue )
	{
		IRI type = IRI.create( strType );
		IRI relation = IRI.create( strRelation );
		IRI value = IRI.create( strValue );

		OWLClass typeCls = dataFactory.getOWLClass( type );
		OWLNamedIndividual valueInd = dataFactory.getOWLNamedIndividual( value );
		OWLObjectProperty opRelation = dataFactory.getOWLObjectProperty( relation );
		OWLObjectHasValue owlObjectHasValue = dataFactory.getOWLObjectHasValue( opRelation, valueInd );
		Set<OWLClassExpression> expSet = new HashSet<OWLClassExpression>();
		expSet.add( typeCls );
		expSet.add( owlObjectHasValue );
		OWLObjectIntersectionOf owlObjectIntersectionOf = dataFactory.getOWLObjectIntersectionOf( expSet );
		Set<OWLNamedIndividual> individuals = reasoner.getInstances( owlObjectIntersectionOf, false ).getFlattened();

		return individuals;
	}

	public OWLObjectHasValue getClassExpressionForObjectProperty( OWLObjectProperty property, OWLIndividual instance )
	{
		OWLObjectHasValue ohv = dataFactory.getOWLObjectHasValue( property, instance );
		return ohv;
	}

	public Set<OWLNamedIndividual> getIndividuals( OWLClass cls )
	{
		Set<OWLNamedIndividual> result = reasoner.getInstances( cls, false ).getFlattened();
		return result;
	}

	public Set<OWLNamedIndividual> getIndividuals( OWLClassExpression clsEx )
	{
		Set<OWLNamedIndividual> result = reasoner.getInstances( clsEx, false ).getFlattened();
		return result;
	}

	public Set<OWLNamedIndividual> getIndividuals( String classIri )
	{
		return getIndividuals( getOWLClass( classIri ) );
	}

	public OWLNamedIndividual getNamedIndividual( String iri )
	{
		OWLNamedIndividual namedIndividual = dataFactory.getOWLNamedIndividual( IRI.create( iri ) );
		return namedIndividual;
	}

	public OWLObjectProperty getObjectProperty( String iri )
	{
		return dataFactory.getOWLObjectProperty( IRI.create( iri ) );
	}

	public Set<OWLClass> getSubClasses( OWLClass owlClass )
	{
		NodeSet<OWLClass> subClasses = reasoner.getSubClasses( owlClass, true );
		Set<OWLClass> classes = subClasses.getFlattened();
		return classes;
	}

	public Set<OWLClass> getSubClasses( OWLClass owlClass, boolean direct )
	{
		NodeSet<OWLClass> subClasses = reasoner.getSubClasses( owlClass, direct );
		Set<OWLClass> classes = subClasses.getFlattened();
		return classes;
	}

	public Set<OWLClass> getSuperClasses( OWLClass owlClass )
	{
		NodeSet<OWLClass> subClasses = reasoner.getSuperClasses( owlClass, true );
		Set<OWLClass> classes = subClasses.getFlattened();
		return classes;
	}

	public Set<OWLClass> getSuperClasses( OWLClass owlClass, boolean direct )
	{
		NodeSet<OWLClass> subClasses = reasoner.getSuperClasses( owlClass, direct );
		Set<OWLClass> classes = subClasses.getFlattened();
		return classes;
	}

	public Set<OWLNamedIndividual> getInstances( OWLClass owlClass )
	{
		NodeSet<OWLNamedIndividual> individualsNodeSet = reasoner.getInstances( owlClass, true );
		Set<OWLNamedIndividual> individuals = individualsNodeSet.getFlattened();
		return individuals;
	}

	public Set<OWLNamedIndividual> getPropertyHolders( OWLNamedIndividual individual, OWLObjectProperty property )
	{
		NodeSet<OWLNamedIndividual> valuesNodeSet = reasoner.getObjectPropertyValues( individual, property );
		Set<OWLNamedIndividual> individuals = valuesNodeSet.getFlattened();
		return individuals;
	}

	private void loadOntocatService() throws OntologyServiceException, URISyntaxException
	{
		long t = System.currentTimeMillis();
		FileOntologyService os1 = new ReasonedFileOntologyService( new URI( "file:" + TRAVEL_ONTOLOGY ) );
		FileOntologyService os2 = new ReasonedFileOntologyService( new URI( "file:" + TRAVEL_FACT_ONTOLOGY ) );

		ontologyService = CompositeDecorator.getService( os1, os2 );
		HopeLogger.log( "## Ontocat service initialized in " + ( System.currentTimeMillis() - t ) / 1000 + " seconds" );
	}

	@Override
	public List<SearchTerm> getOntologyTerms( List<SearchTerm> searchTerms )
	{
		List<SearchTerm> ontologyTerms = new ArrayList<SearchTerm>();

		for ( SearchTerm searchTerm : searchTerms )
		{
			List<OntologyTerm> ontocatTerms;
			try
			{
				ontocatTerms = ontologyService.searchAll( searchTerm.getWord() );
				for ( OntologyTerm ontologyTerm : ontocatTerms )
				{
					OntoTerm ontoTerm = new OntoTerm();
					ontoTerm.setIri( ontologyTerm.getOntologyAccession() + ontologyTerm.getAccession() );
					ontoTerm.setType( ontologyTerm.getOwlEntity().getClass().toString() );
					searchTerm.getOntologyTerms().add( ontoTerm );
				}

				ontologyTerms.add( searchTerm );
			}
			catch ( OntologyServiceException e )
			{
				HopeLogger.log( e.getMessage() );
			}
		}

		return ontologyTerms;
	}

}
